package org.apache.lucene.ocean;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.lucene.ocean.snapshotlog.SnapshotLogManager;
import org.apache.lucene.ocean.util.SortedListMap;
import org.apache.lucene.ocean.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Snapshots {
  final static Logger LOG = LoggerFactory.getLogger(Snapshots.class);
  private SortedListMap<BigDecimal,Snapshot> list = new SortedListMap<BigDecimal,Snapshot>();
  private TransactionSystem system;

  public Snapshots(TransactionSystem system) {
    this.system = system;
  }

  public synchronized boolean hasRefs(Long snapshotId) {
    Snapshot snapshot = get(snapshotId, false);
    if (snapshot == null)
      return false;
    return snapshot.hasRefs();
  }

  public synchronized void remove(int max, long durationMillis) {
    if (list.size() > max) {
      long now = System.currentTimeMillis();
      int numToCheck = list.size() - max;
      Iterator<Snapshot> iterator = list.values().iterator();
      for (int x = 0; x < numToCheck; x++) {
        Snapshot snapshot = iterator.next();
        if (!snapshot.hasRefs() && (snapshot.getTimestamp() + durationMillis) > now) {
          iterator.remove();
        }
      }
    }
  }

  /**
   * Loads the ids from the file names rather than loading each xml file.
   * 
   * @param directory
   * @return
   * @throws Exception
   * 
   * public static List<BigDecimal> loadSnapshotInfoIds(LogDirectory directory)
   * throws Exception { List<BigDecimal> list = new ArrayList<BigDecimal>();
   * for (String file : directory.list()) { if (directory.fileLength(file) > 0) {
   * String str = "snapshot_"; if (file.startsWith(str)) { String main =
   * file.substring(str.length(), file.lastIndexOf('.')); String[] split =
   * StringUtils.split(main, "_"); if (split.length > 1) { String replace =
   * main.replace('_', '.'); //System.out.println("replace: "+replace);
   * list.add(new BigDecimal(replace)); } else { Long snapshotId = new
   * Long(split[0]); list.add(new BigDecimal(snapshotId)); } } } }
   * Collections.sort(list); return list; }
   * 
   * public static List<SnapshotInfo> loadSnapshotInfos(LogDirectory directory)
   * throws Exception { List<SnapshotInfo> snapshotInfos = new ArrayList<SnapshotInfo>();
   * for (String file : directory.list()) { if (directory.fileLength(file) > 0) {
   * String str = "snapshot_"; if (file.startsWith(str)) { String main =
   * file.substring(str.length(), file.lastIndexOf('.')); String[] split =
   * StringUtils.split(main, "_"); Long snapshotId = new Long(split[0]); Integer
   * version = new Integer(0); if (split.length > 1) version = new
   * Integer(split[1]); String xml = Util.getString(file, directory); Element
   * element = XMLUtil.parseElement(xml); snapshotInfos.add(new
   * SnapshotInfo(element)); // sorted.add(new BigDecimal(snapshotId + "." +
   * version)); } } } Collections.sort(snapshotInfos); return snapshotInfos; }
   * 
   * public static SnapshotInfo loadMaxSnapshotInfo(LogDirectory directory)
   * throws Exception { List<BigDecimal> ids = loadSnapshotInfoIds(directory);
   * if (ids.size() == 0) return null; BigDecimal maxId = Util.max(ids); String
   * fileName = Snapshot.getFileName(maxId); String xml =
   * Util.getString(fileName, directory); Element element =
   * XMLUtil.parseElement(xml); return new SnapshotInfo(element); }
   */
  private synchronized Snapshot get(long snapshotId, boolean incref) {
    List<Snapshot> snapshots = getForSnapshot(snapshotId, false);
    Snapshot snapshot = Util.max(snapshots);
    snapshot.incRef();
    return snapshot;
  }
  
  public synchronized Snapshot get(long snapshotId) {
    return get(snapshotId, true);
  }
  
  private synchronized List<Snapshot> getForSnapshot(long snapshotId, boolean incref) {
    List<Snapshot> inrange = new ArrayList<Snapshot>();
    Iterator<? extends Map.Entry<BigDecimal,Snapshot>> iterator = list.iterator(new BigDecimal(snapshotId), true, new BigDecimal(snapshotId+1), false);
    while (iterator.hasNext()) {
      Snapshot snapshot = iterator.next().getValue();
      inrange.add(snapshot);
    }
    return inrange;
  }
  
  /**
  public synchronized List<Snapshot> getForSnapshot(long snapshotId) {
    List<Snapshot> inrange = new ArrayList<Snapshot>();
    for (Snapshot snapshot : list.values()) {
      long l = snapshot.getId().toBigInteger().longValue();
      if (l == snapshotId) {
        inrange.add(snapshot);
      }
    }
    return inrange;
  }
  **/
  public synchronized boolean contains(BigDecimal id) {
    return list.containsKey(id);
  }

  public synchronized boolean contains(Long snapshotId) {
    return get(snapshotId, false) != null;
  }

  /**
   * public synchronized boolean containsIndex(long indexid) { for (Snapshot
   * snapshot : list) { if (snapshot.containsIndex(indexid)) return true; }
   * return false; }
   */
  /**
  private void remove(Snapshot snapshot) throws IOException {
    Iterator<Snapshot> iterator = list.values().iterator();
    while (iterator.hasNext()) {
      Snapshot s = iterator.next();
      if (s.getId().equals(snapshot.getId())) {
        iterator.remove();
        String file = Snapshot.getFileName(snapshot.getId());
        system.directoryMap.getDirectory().deleteFile(file);
      }
    }
  }
  **/
  public synchronized Snapshot getLatestSnapshot() {
    return getLatestSnapshot(true);
  }
  
  private synchronized Snapshot getLatestSnapshot(boolean incref) {
    if (list.size() == 0)
      return null;
    Snapshot snapshot = list.get(list.size() - 1);
    if (incref) snapshot.incRef();
    return snapshot;
  }

  synchronized void add(Snapshot snapshot, boolean createFile) throws Exception {
    // verify on minor version snapshot numdocs is the same
    if (snapshot.isMinor()) {
      // assert snapshot.numDocs() == getLatestSnapshot().numDocs();
    }
    if (createFile) {
      addCreateFile(snapshot);
    } else {
      list.put(snapshot.getId(), snapshot);
    }
    LOG.info("snapshot: " + snapshot.toString() + " added");
  }

  private void addCreateFile(Snapshot snapshot) throws Exception {
    BigDecimal id = snapshot.getId();
    SnapshotInfo snapshotInfo = snapshot.getSnapshotInfo();
    SnapshotLogManager snapshotLogManager = system.getSnapshotLogManager();
    snapshotLogManager.write(snapshotInfo);
    list.put(id, snapshot);
  }

  /**
   * private void addCreateFile(Snapshot snapshot) throws Exception { BigDecimal
   * id = snapshot.getId(); SnapshotInfo snapshotInfo =
   * snapshot.getSnapshotInfo(); String fileName = Snapshot.getFileName(id);
   * LogDirectory directory = system.directoryMap.getDirectory(); if
   * (directory.fileExists(fileName)) { throw new IOException("fileName: " +
   * fileName + " already exists"); } RandomAccessFile output =
   * directory.getOutput(fileName, true); Element element =
   * snapshotInfo.toElement(); String xml =
   * XMLUtil.outputElementOmitDeclaration(element); byte[] bytes =
   * xml.getBytes("UTF-8"); list.add(snapshot); output.write(bytes, 0,
   * bytes.length); output.close(); // remove previous versions for this id //
   * SortedMap<BigDecimal,Snapshot> headMap = snapshotMap.headMap(id); // for
   * (Snapshot removeSnapshot : headMap.values()) { // remove(removeSnapshot); // }
   * Collections.sort(list); }
   */
}